package com.mall.gateway.filter;

import com.mall.gateway.config.GatewayAuthProperties;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

/**
 * 全局过滤器
 */
@Slf4j
@Component
public class GlobalAuthFilter implements GlobalFilter, Ordered {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;

    @Resource
    private GatewayAuthProperties gatewayAuthProperties;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        String path = request.getURI().getPath();

        log.info("网关过滤器处理请求路径: {}", path);

        // OPTIONS请求直接放行（CORS预检请求）- 在所有检查之前
        if (request.getMethod() != null && "OPTIONS".equals(request.getMethod().name())) {
            log.info("OPTIONS预检请求，直接放行：{}", path);
            return chain.filter(exchange);
        }

        if (isWhitePath(path)) {
            log.info("白名单路径，跳过认证：{}", path);
            return chain.filter(exchange);
        }

        // 获取 Session ID
        String sessionId = getSessionId(request);
        // 从 Redis 获取用户信息
        try {
            Map<String, Object> userInfo = getUserFromRedis(sessionId);
            if (userInfo == null) {
                log.error("Session 已过期或无效：{}", sessionId);
                return Mono.error(new ResponseStatusException(HttpStatus.UNAUTHORIZED, "会话已过期，请重新登录"));
            }

            // 权限检查
            if (!hasPermission(userInfo, path)) {
                log.error("用户 {} 访问路径 {} 被拒绝", userInfo.get("username"), path);
                return Mono.error(new ResponseStatusException(HttpStatus.FORBIDDEN, "权限不足"));
            }

            log.info("用户认证成功：{}, 路径：{}", userInfo.get("username"), path);
            return chain.filter(exchange);

        } catch (Exception e) {
            log.error("认证过程中发生异常: ", e);
            return Mono.error(new ResponseStatusException(HttpStatus.UNAUTHORIZED, "认证失败"));
        }
    }

    /**
     * 检查是否为白名单路径
     */
    private boolean isWhitePath(String path) {
        List<String> whitePaths = gatewayAuthProperties.getWhitePaths();
        if (whitePaths == null || whitePaths.isEmpty()) {
            return false;
        }
        return whitePaths.stream().anyMatch(path::startsWith);
    }

    /**
     * 获取 Session ID
     */
    private String getSessionId(ServerHttpRequest request) {
        // 1) 优先从 Cookie 读取（MALL_SESSION 或 SESSION）
        try {
            MultiValueMap<String, HttpCookie> cookies = request.getCookies();
            HttpCookie cookie = cookies.getFirst("MALL_SESSION");
            if (cookie == null) {
                cookie = cookies.getFirst("SESSION");
            }
            if (cookie != null) {
                String raw = normalizeSessionId(cookie.getValue());
                return raw;
            }
        } catch (Exception ignored) {}
        
        // 2) 兜底从请求头读取（便于非浏览器客户端或排障）
        try {
            List<String> headerIds = request.getHeaders().get("X-Session-Id");
            if (headerIds != null && !headerIds.isEmpty()) {
                return normalizeSessionId(headerIds.getFirst());
            }
        } catch (Exception ignored) {}
        
        return null;
    }

    /**
     * 归一化 SessionId
     */
    private String normalizeSessionId(String value) {
        if (value == null) {
            return null;
        }
        String v = value.trim();
        if (v.startsWith("\"") && v.endsWith("\"")) {
            v = v.substring(1, v.length() - 1);
        }
        try {
            v = URLDecoder.decode(v, StandardCharsets.UTF_8);
        } catch (Exception ignored) {
        }
        
        // 尝试 Base64 URL-safe 解码
        String decoded = tryDecodeBase64UrlSafe(v);
        if (isLikelyUuid(decoded)) {
            return decoded;
        }
        
        // 非 Base64 或解码结果不像 UUID，则直接返回原值
        return v;
    }

    private String tryDecodeBase64UrlSafe(String v) {
        try {
            byte[] decoded = Base64.getUrlDecoder().decode(v);
            return new String(decoded, StandardCharsets.UTF_8);
        } catch (Exception ignored) {
            return v;
        }
    }

    private static final Pattern UUID_LIKE = Pattern.compile("[0-9a-fA-F\\-]{32,64}");

    private boolean isLikelyUuid(String s) {
        if (s == null) {
            return false;
        }
        return UUID_LIKE.matcher(s).matches();
    }

    /**
     * 从 Redis 获取用户信息
     */
    @SuppressWarnings("unchecked")
    private Map<String, Object> getUserFromRedis(String sessionId) {
        try {
            String sessionKey = "mall:session:sessions:" + sessionId;
            // Spring Session 将会话属性以 sessionAttr:<attrName> 存储在 Hash 中
            Object sessionData = redisTemplate.opsForHash().get(sessionKey, "sessionAttr:user_login");

            if (sessionData instanceof Map) {
                Map<String, Object> userInfo = (Map<String, Object>) sessionData;

                // 活跃续期逻辑
                renewSessionIfNeeded(sessionKey, userInfo);

                return userInfo;
            }
            return null;
        } catch (Exception e) {
            log.error("从 Redis 获取用户失败：", e);
            return null;
        }
    }
    
    /**
     * 权限检查
     */
    private boolean hasPermission(Map<String, Object> userInfo, String path) {
        String userType = String.valueOf(userInfo.get("user_type"));

        // 被封禁用户直接拒绝
        if ("ban".equals(userType)) {
            return false;
        }

        // 管理员路径需要管理员权限
        if (isAdminPath(path)) {
            return "admin".equals(userType);
        }

        // 其他路径，登录用户都可以访问
        return true;
    }

    /**
     * 检查是否为管理员路径
     */
    private boolean isAdminPath(String path) {
        List<String> adminPaths = gatewayAuthProperties.getAdminPaths();
        if (adminPaths == null || adminPaths.isEmpty()) {
            return false;
        }
        return adminPaths.stream().anyMatch(path::startsWith);
    }

    /**
     * 根据需要续期Session
     */
    private void renewSessionIfNeeded(String sessionKey, Map<String, Object> userInfo) {
        try {
            Long ttl = redisTemplate.getExpire(sessionKey, TimeUnit.SECONDS);
            String userType = String.valueOf(userInfo.get("user_type"));

            // 根据用户类型获取基础TTL
            int baseTTL = calculateBaseTTL(userType);

            // 如果剩余时间少于总时间的1/3，则续期
            if (ttl != null && ttl > 0 && ttl < baseTTL / 3) {
                redisTemplate.expire(sessionKey, baseTTL, TimeUnit.SECONDS);
                log.debug("Session续期成功: {}, 新TTL: {}秒", sessionKey, baseTTL);
            }
        } catch (Exception e) {
            log.warn("Session续期失败: {}", e.getMessage());
        }
    }

    /**
     * 根据用户类型计算基础TTL
     */
    private int calculateBaseTTL(String userType) {
        if ("admin".equals(userType)) {
            return 60 * 60;      // 管理员: 1小时
        } else {
            return 2 * 60 * 60;  // 普通用户: 2小时
        }
    }

    @Override
    public int getOrder() {
        return -50; // 调整优先级，让CORS过滤器先执行
    }
}
